﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;

namespace VietThinks.Web.Mvc
{
    public static partial class HtmlHelperExtensions
    {
        /// <summary>
        /// Used with "using". Starting a BindingPrefixSection with the prefix provided via expression. 
        /// HtmlHelper extension methods provided in VietThinks.Utilities.Web.Mvc.HtmlHelperExtensions
        /// should append the prefix defined to their binding path (in input control's name field).
        /// </summary>
        /// <typeparam name="TModel">Model type</typeparam>
        /// <param name="htmlHelper">Self</param>
        /// <param name="expression">Prefix</param>
        /// <param name="indices">Index values used in prefix</param>
        /// <returns>BindingSection</returns>
        internal static BindingSection BeginBindingSection<TModel>(this HtmlHelper htmlHelper, Expression<Func<TModel, object>> prefixExpression) 
        {
            string prefix = BuildBindingPath(htmlHelper, prefixExpression);

            return BindingSection.Begin(GetBindingSectionContext(htmlHelper), prefix);
        }

        internal static void EndBindingSection(this HtmlHelper htmlHelper) 
        {
            BindingSection.End(GetBindingSectionContext(htmlHelper));
        }
        
        internal static object GetBindingSectionContext(this HtmlHelper htmlHelper) 
        {
            return System.Web.HttpContext.Current.GetHashCode(); //htmlHelper.ViewContext.HttpContext.GetHashCode(); // binding section context per HttpContext
        }
    }

    internal class BindingSection : IDisposable
    {
        internal BindingSection(object context, string prefix)
        {
            this.context = context;

            // ensure prefix stack is ready
            lock (prefixDictionary)
            {
                if (!prefixDictionary.ContainsKey(this.Context))
                {
                    prefixDictionary.Add(this.Context, new Stack<string>());
                }
            }

            string lastPrefix = prefixDictionary[this.Context].Count > 0 ? prefixDictionary[this.Context].Peek() : String.Empty;

            string fullPrefix = (lastPrefix.Trim('.') + "." + prefix.Trim('.')).Trim('.');

            prefixDictionary[this.Context].Push(fullPrefix);
        }

        private readonly object context;

        public object Context { get { return this.context; } }

        private static readonly IDictionary<object, Stack<string>> prefixDictionary = new Dictionary<object, Stack<string>>();

        internal IDictionary<object, Stack<string>> PrefixDictionary
        {
            get { return prefixDictionary; }
        }

        public static BindingSection Begin(object context, string prefix)
        {
            return new BindingSection(context, prefix);
        }

        public static void End(object context)
        {
            lock (prefixDictionary)
            {
                if (prefixDictionary.ContainsKey(context))
                {
                    if (prefixDictionary[context].Count > 0)
                    {
                        prefixDictionary[context].Pop();
                    }

                    // remove context if the last prefix has been removed. No empty
                    // prefix stack should be maintained
                    if (prefixDictionary[context].Count == 0)
                    {
                        prefixDictionary.Remove(context);
                    }
                }
            }

        }

        public static string GetCurrentBindingPrefix(object context)
        {
            lock (prefixDictionary)
            {
                if (prefixDictionary.ContainsKey(context))
                {
                    // context's prefix stack should never be empty
                    Debug.Assert(prefixDictionary[context].Count > 0);
                    
                    return prefixDictionary[context].Peek();
                }
            }

            return String.Empty;
        }

        public void End()
        {
            End(this.Context);
        }

        #region IDisposable Members

        void IDisposable.Dispose()
        {
            this.End();
        }

        #endregion
    }
}
